﻿using System;

namespace Effects.Effects.Tools
{
    /// <summary>
    ///  Oscillating delay line with min delay length.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   Similar to the OscillatingDelayLine but it's delay time is not in range 0-max, but in range min-max.
    ///  </para>
    ///  <para>
    ///   This behaviour is set by minDelayLength and varyingDelayLength members. MinDelayLength says minimal length of delay and maximal delay length is equal to
    ///   minDelayLength + varyingDelayLength. So varayingDelayLength tells us the varying part of delay length.
    ///  </para>
    /// </remarks>
    public class OscillatingDelayLineMinLength:OscillatingDelayLine
    {
        /// <summary>
        ///  Minimal delay length in samples.
        /// </summary>
        private int minDelayLength;

        /// <summary>
        ///  Part in which delay length may vary.
        /// </summary>
        private int varyingDelayLength;

        /// <summary>
        ///  Sampling rate of input signal.
        /// </summary>
        private int sampleRate;

        /// <summary>
        ///  Sets the leght of the delay length's varying interval in miliseconds.
        /// </summary>
        public double VaryingTime
        {
            set
            {
                this.varyingDelayLength = Convert.ToInt32(value * sampleRate / 1000);
            }
        }

        /// <summary>
        ///  Creates Oscillating delay line with given mininmal delay lenght and length of varying interval (both in samples), sampling rate and <see cref="Oscillator"/> 
        ///  that controls varying part of delat length.
        /// </summary>
        /// <param name="minDelayLength"> Minimal delay length in samples. </param>
        /// <param name="varyingInterval"> Length of delay length's varying interval in samples </param>
        /// <param name="sampleRate"> Sampling rate of an input signal. </param>
        /// <param name="osc"> Oscillator controlling varying delay length. </param>
        public OscillatingDelayLineMinLength(int minDelayLength, int varyingInterval, int sampleRate, Oscillator osc)
        {
            this.minDelayLength = minDelayLength;
            this.varyingDelayLength = varyingInterval;
            length = minDelayLength + varyingDelayLength;

            this.sampleRate = sampleRate;
            stepTime = 1000 / (double)sampleRate;
            oscillator = osc;

            data = new double[length];
            index = 0;
        }

        /// <summary>
        ///  Creates Oscillating delay line with given mininmal delay lenght and length of varying interval (both in miliseconds), sampling rate and <see cref="Oscillator"/> 
        ///  that controls varying part of delat length.
        /// </summary>
        /// <param name="minDelayTime"> Minimal delay length in miliseconds. </param>
        /// <param name="varyingTimeInterval"> Length of delay length's varying interval in miliseconds </param>
        /// <param name="sampleRate"> Sampling rate of an input signal. </param>
        /// <param name="osc"> Oscillator controlling varying delay length. </param>
        public OscillatingDelayLineMinLength(double minDelayTime, double varyingTimeInterval, int sampleRate, Oscillator osc)
        {

            minDelayLength = Convert.ToInt32(minDelayTime * sampleRate / 1000);
            varyingDelayLength = Convert.ToInt32(varyingTimeInterval * sampleRate / 1000);
            length = minDelayLength + varyingDelayLength;

            this.sampleRate = sampleRate;
            stepTime = 1000 / (double) sampleRate;

            oscillator = osc;
            data = new double[length];
            index = 0;
        }

        /// <summary>
        ///  Performs one step of the delay line. Puts given sample to the input of line and returns current output.
        /// </summary>
        /// <param name="sample"> Sample to put into delay line. </param>
        /// <returns> Current output sample from delay line. </returns>
        public override double Step(double sample)
        {
            double delayTime = minDelayLength - 2 + varyingDelayLength  * Math.Abs(oscillator.Step(stepTime));
            int delayTimeIndex = Convert.ToInt32(Math.Floor(delayTime));
            double delayTimeIndexPart = delayTime - delayTimeIndex;

            double ret;
            if (delayTimeIndexPart != 0)
            {
                /*
                ret = delayTimeIndexPart * data[(index + delayTimeIndex) % length];
                ret += (1 - delayTimeIndexPart) * data[(index + delayTimeIndex + 1) % length];
                 * */

                // firt order all pass filter interpolation
                //ret = delayTimeIndexPart * data[(index + delayTimeIndex) % length];
                //ret += data[(index + delayTimeIndex + 1) % length];
                //ret -= delayTimeIndexPart * lastOut;
                //lastOut = ret;

                // compute interpolation right-floor
                int index0 = (index + delayTimeIndex);
                int index_1 = index0 - 1;
                int index1 = index0 + 1;
                int index2 = index0 + 2;

                index0 %= length;
                index_1 %= length;
                index1 %= length;
                index2 %= length;



                // better interpolation
                // get neighbourgh samples
                double y_1 = data[index_1];
                double y0 = data[index0];
                double y1 = data[index1];
                double y2 = data[index2];


                // compute interpolation x

                // calculate
                double c0 = y0;
                double c1 = 0.5f * (y1 - y_1);
                double c2 = y_1 - 2.5f * y0 + 2.0f * y1 - 0.5f * y2;
                double c3 = 0.5f * (y2 - y_1) + 1.5f * (y0 - y1);

                ret = ((c3 * delayTimeIndexPart + c2) * delayTimeIndexPart + c1) * delayTimeIndexPart + c0;
            }
            else
                ret = data[(index + delayTimeIndex) % length];

            data[index] = sample;

            index--;
            if (index < 0)
                index += length;
            return ret;
        }
    }
}